//: concurrency/SynchronizationComparisons.java
// Comparing the performance of explicit Locks
// and Atomics versus the synchronized keyword.
package concurrency;

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
import java.util.*;

import static net.mindview.util.Print.*;

abstract class Accumulator
{
    public static long cycles = 50000L;
    
    // Number of Modifiers and Readers during each test:
    private static final int N = 4;
    
    public static ExecutorService exec = Executors.newFixedThreadPool(N * 2);
    
    private static CyclicBarrier barrier = new CyclicBarrier(N * 2 + 1);
    
    protected volatile int index = 0;
    
    protected volatile long value = 0;
    
    protected long duration = 0;
    
    protected String id = "error";
    
    protected final static int SIZE = 100000;
    
    protected static int[] preLoaded = new int[SIZE];
    
    static
    {
        // Load the array of random numbers:
        Random rand = new Random(47);
        for (int i = 0; i < SIZE; i++)
            preLoaded[i] = rand.nextInt();
    }
    
    public abstract void accumulate();
    
    public abstract long read();
    
    private class Modifier implements Runnable
    {
        public void run()
        {
            for (long i = 0; i < cycles; i++)
                accumulate();
            try
            {
                barrier.await();
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
    }
    
    private class Reader implements Runnable
    {
        private volatile long value;
        
        public void run()
        {
            for (long i = 0; i < cycles; i++)
                value = read();
            try
            {
                barrier.await();
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
    }
    
    public void timedTest()
    {
        long start = System.nanoTime();
        for (int i = 0; i < N; i++)
        {
            exec.execute(new Modifier());
            exec.execute(new Reader());
        }
        try
        {
            barrier.await();
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
        duration = System.nanoTime() - start;
        printf("%-13s: %13d\n", id, duration);
    }
    
    public static void report(Accumulator acc1, Accumulator acc2)
    {
        printf("%-22s: %.2f\n", acc1.id + "/" + acc2.id, (double)acc1.duration / (double)acc2.duration);
    }
}

class BaseLine extends Accumulator
{
    {
        id = "BaseLine";
    }
    
    public void accumulate()
    {
        value += preLoaded[index++];
        if (index >= SIZE)
            index = 0;
    }
    
    public long read()
    {
        return value;
    }
}

class SynchronizedTest extends Accumulator
{
    {
        id = "synchronized";
    }
    
    public synchronized void accumulate()
    {
        value += preLoaded[index++];
        if (index >= SIZE)
            index = 0;
    }
    
    public synchronized long read()
    {
        return value;
    }
}

class LockTest extends Accumulator
{
    {
        id = "Lock";
    }
    
    private Lock lock = new ReentrantLock();
    
    public void accumulate()
    {
        lock.lock();
        try
        {
            value += preLoaded[index++];
            if (index >= SIZE)
                index = 0;
        }
        finally
        {
            lock.unlock();
        }
    }
    
    public long read()
    {
        lock.lock();
        try
        {
            return value;
        }
        finally
        {
            lock.unlock();
        }
    }
}

class AtomicTest extends Accumulator
{
    {
        id = "Atomic";
    }
    
    private AtomicInteger index = new AtomicInteger(0);
    
    private AtomicLong value = new AtomicLong(0);
    
    public void accumulate()
    {
        // Oops! Relying on more than one Atomic at
        // a time doesn't work. But it still gives us
        // a performance indicator:
        int i = index.getAndIncrement();
        value.getAndAdd(preLoaded[i]);
        if (++i >= SIZE)
            index.set(0);
    }
    
    public long read()
    {
        return value.get();
    }
}

public class SynchronizationComparisons
{
    static BaseLine baseLine = new BaseLine();
    
    static SynchronizedTest synch = new SynchronizedTest();
    
    static LockTest lock = new LockTest();
    
    static AtomicTest atomic = new AtomicTest();
    
    static void test()
    {
        print("============================");
        printf("%-12s : %13d\n", "Cycles", Accumulator.cycles);
        baseLine.timedTest();
        synch.timedTest();
        lock.timedTest();
        atomic.timedTest();
        Accumulator.report(synch, baseLine);
        Accumulator.report(lock, baseLine);
        Accumulator.report(atomic, baseLine);
        Accumulator.report(synch, lock);
        Accumulator.report(synch, atomic);
        Accumulator.report(lock, atomic);
    }
    
    public static void main(String[] args)
    {
        int iterations = 5; // Default
        if (args.length > 0) // Optionally change iterations
            iterations = new Integer(args[0]);
        // The first time fills the thread pool:
        print("Warmup");
        baseLine.timedTest();
        // Now the initial test doesn't include the cost
        // of starting the threads for the first time.
        // Produce multiple data points:
        for (int i = 0; i < iterations; i++)
        {
            test();
            Accumulator.cycles *= 2;
        }
        Accumulator.exec.shutdown();
    }
} /*
   * Output: (Sample) Warmup BaseLine : 34237033 ============================ Cycles : 50000 BaseLine : 20966632
   * synchronized : 24326555 Lock : 53669950 Atomic : 30552487 synchronized/BaseLine : 1.16 Lock/BaseLine : 2.56
   * Atomic/BaseLine : 1.46 synchronized/Lock : 0.45 synchronized/Atomic : 0.79 Lock/Atomic : 1.76
   * ============================ Cycles : 100000 BaseLine : 41512818 synchronized : 43843003 Lock : 87430386 Atomic :
   * 51892350 synchronized/BaseLine : 1.06 Lock/BaseLine : 2.11 Atomic/BaseLine : 1.25 synchronized/Lock : 0.50
   * synchronized/Atomic : 0.84 Lock/Atomic : 1.68 ============================ Cycles : 200000 BaseLine : 80176670
   * synchronized : 5455046661 Lock : 177686829 Atomic : 101789194 synchronized/BaseLine : 68.04 Lock/BaseLine : 2.22
   * Atomic/BaseLine : 1.27 synchronized/Lock : 30.70 synchronized/Atomic : 53.59 Lock/Atomic : 1.75
   * ============================ Cycles : 400000 BaseLine : 160383513 synchronized : 780052493 Lock : 362187652 Atomic
   * : 202030984 synchronized/BaseLine : 4.86 Lock/BaseLine : 2.26 Atomic/BaseLine : 1.26 synchronized/Lock : 2.15
   * synchronized/Atomic : 3.86 Lock/Atomic : 1.79 ============================ Cycles : 800000 BaseLine : 322064955
   * synchronized : 336155014 Lock : 704615531 Atomic : 393231542 synchronized/BaseLine : 1.04 Lock/BaseLine : 2.19
   * Atomic/BaseLine : 1.22 synchronized/Lock : 0.47 synchronized/Atomic : 0.85 Lock/Atomic : 1.79
   * ============================ Cycles : 1600000 BaseLine : 650004120 synchronized : 52235762925 Lock : 1419602771
   * Atomic : 796950171 synchronized/BaseLine : 80.36 Lock/BaseLine : 2.18 Atomic/BaseLine : 1.23 synchronized/Lock :
   * 36.80 synchronized/Atomic : 65.54 Lock/Atomic : 1.78 ============================ Cycles : 3200000 BaseLine :
   * 1285664519 synchronized : 96336767661 Lock : 2846988654 Atomic : 1590545726 synchronized/BaseLine : 74.93
   * Lock/BaseLine : 2.21 Atomic/BaseLine : 1.24 synchronized/Lock : 33.84 synchronized/Atomic : 60.57 Lock/Atomic :
   * 1.79
   */// :~
